#ifndef ABSTRACT_PARSER_H
#define ABSTRACT_PARSER_H
#pragma once

#include <type_traits>
#include <variant>
#include "bail_error_strategy.h"
#include "syntax_error.h"

namespace cyclone::parser
{

	enum class ParseMode
	{
		Formal,
		Loose,
	};

	using antlr4::ANTLRInputStream;
	using antlr4::CommonTokenStream;
	template <class LexerImpl, class ParserImpl, ParseMode _Mode>
	class AbstractParser : public ParserImpl
	{
	public:
		DISALLOW_COPY_AND_ASSIGN(AbstractParser)
		AbstractParser() : ParserImpl(nullptr)
		{
			static_assert(std::is_base_of_v<antlr4::Lexer, LexerImpl>);
			static_assert(std::is_base_of_v<antlr4::Parser, ParserImpl>);
			if constexpr (ParseMode::Formal == _Mode)
			{
				removeErrorListeners();
				bailErrorStrategy_.reset(new BailErrorStrategy);
				setErrorHandler(bailErrorStrategy_);
			}
		};
		virtual ~AbstractParser() {}
		void init(const std::string &textStream)
		{
			input_.reset(new ANTLRInputStream(textStream));
			lexer_.reset(new LexerImpl(input_.get()));
			tokenStream_.reset(new CommonTokenStream(lexer_.get()));
			tokenStream_->fill();
			setInputStream(tokenStream_.get());
		}
		template <auto Rule>
		auto applyRule() -> std::add_lvalue_reference_t<decltype(*(std::bind(Rule, this)()))>
		{
			if constexpr (_Mode == ParseMode::Loose)
				return *std::bind(Rule, this)();
			else
			{
				decltype(std::bind(Rule, this)()) parserTree = nullptr;
				try
				{
					parserTree = std::bind(Rule, this)();
				}
				catch (SyntaxError &e)
				{
					throw;
				}
				catch (...)
				{
					SyntaxError::handleNonSemanticError(this);
				}
				return *parserTree;
			}
		}

	protected:
		/**
		 * NOTE None of the members is owned by another
		 */
		std::unique_ptr<ANTLRInputStream> input_ = nullptr;

		std::unique_ptr<LexerImpl> lexer_ = nullptr;

		std::unique_ptr<CommonTokenStream> tokenStream_ = nullptr;

	private:
		std::shared_ptr<BailErrorStrategy> bailErrorStrategy_ = nullptr;
	};

} // namespace cyclone::parser
#endif
